<link rel="import" href="web-animations.html">
<link rel="import" href="polymer-animation-keyframe.html">
<link rel="import" href="polymer-animation-prop.html">
<polymer-element name="polymer-animation">
  <script>
    (function() {
      function findTarget(inSelector, inNode) {
        var p = inNode;
        var target;
        while (p && !p.host && p !== document) {
          p = p.parentNode;
        }
        if (p) {
          target = p.querySelector(inSelector);
        }
        if (!target && p.host) {
          target = findTarget(inSelector, p.host);
        }
        return target;
      };
      function toNumber(value, allowInfinity) {
        return (allowInfinity && value === 'Infinity') ? Number.POSITIVE_INFINITY : Number(value);
      };
      /**
       * WebAnimations module.
       * @module Animation
       * @main animation
       * @example toolkitchen/labs/animation2/grid-fade.html
       * @example toolkitchen/labs/animation2/group.html
       */
      /**
       * Component for a single animation.
       *
       * A animation component to fade out an element:
       *
       *     <polymer-animation id="fadeout">
       *       <polymer-animation-prop name="opacity">
       *         <polymer-animation-keyframe value="0" offset="0"></polymer-animation-keyframe>
       *         <polymer-keyframe value="1" offset="1"></polymer-animation-keyframe>
       *       <polymer-animation-prop>
       *     </polymer-animation>
       * @class polymer-animation
       */
       /**
        * Fired when an animation completes
        * @event complete
        */
      Polymer('polymer-animation', {
        publish: {
          /**
           * The node being animated.
           * @property target
           * @type HTMLElement|Node
           */
          target: null,
          /**
           * Selector for the node being animated.
           * @property targetSelector
           * @type String
           */
          targetSelector: null,
          // animation
          /**
           * Animation properties specified as a dictionary of
           * <css properties>:<array of values>. For example,
           * `{opacity: ['0','1']}` specifies animating the opacity
           * from 0 to 1.
           * The values must be strings.
           * (https://github.com/web-animations/web-animations-js/issues/178)
           * @property properties
           * @type Object
           */
          properties: null,
          //accumulate: null, // not working in polyfill
          /**
           * The composition behavior. "replace", "add" or "merge".
           * @property composite
           * @type String
           */
          composite: 'replace',
          // timing
          /**
           * Animation duration in milliseconds or 'infinity'.
           * @property duration
           * @type Number|'Infinity'
           */
          duration: 0.5,
          /**
           * "none", "forwards", "backwards" or "both".
           * @property fillMode
           * @type String
           */
          fillMode: 'forwards',
          /**
           * A transition timing function.
           * @property easing
           * @type String
           */
          easing: 'linear',
          /**
           * Number of iterations into the timed item in which to begin
           * the animation. e.g. 0.5 will cause the animation to begin
           * halfway through the first iteration.
           * @property iterationStart
           * @type Number
           */
          iterationStart: 0,
          /**
           * @property iterationCount
           * @type Number|'Infinity'
           */
          iterationCount: 1,
          /**
           * Start delay in milliseconds.
           * @property startDelay
           * @type Number
           */
          startDelay: 0,
          /**
           * "normal", "reverse", "alternate" or "alternate-reverse".
           * @property direction
           * @type String
           */
          direction: 'normal',
          /**
           * @property autoplay
           * @type Boolean
           */
          autoplay: false
        },
        animation: false,
        ready: function() {
          this.asyncApply();
        },
        /**
         * Plays the animation.
         * @method play
         */
        play: function() {
          // need to flush to make sure all side effects are applied
          // before playing the animation.
          Platform.flush();
          this.asyncMethod(function() {
            this.completeApply();
            //this.animation && console.log('play', this.animation);
            if (this.animation) {
              this.cancel();
              this.player = document.timeline.play(this.animation);
              this.player.paused = false;
              this.player.currentTime = 0;
              this.monitor();
            }
          });
        },
        /**
         * Stops the animation.
         * @method cancel
         */
        cancel: function() {
          if (this.player) {
            this.player.source = null;
          }
        },
        apply: function() {
          this.cancelApply();
          this.animation = null;
          this.animation = this.makeAnimation();
          if (this.autoplay && this.animation) {
            this.play();
          }
          return this.animation;
        },
        makeAnimation: function() {
          //this.target && console.log('makeAnimation target', this.target, 'animation', this.animationProps, 'timing', this.timingProps);
          return this.target ? new Animation(this.target, this.animationProps, this.timingProps) : null;
        },
        asyncApply: function() {
          this.cancelApply();
          this.applyJob = this.job(this.applyJob, this.apply);
        },
        cancelApply: function() {
          this.applyJob && this.applyJob.stop();
          this.applyJob = null;
        },
        completeApply: function() {
          this.applyJob && this.applyJob.complete();
        },
        animationChanged: function() {
          // TODO: attributes are not case sensitive.
          // TODO: Sending 'this' with the event because if the children is
          // in ShadowDOM the sender becomes the shadow host.
          this.fire('animationchange', this);
        },
        targetChanged: function() {
          this.asyncApply();
        },
        targetSelectorChanged: function() {
          if (this.targetSelector) {
            this.target = findTarget(this.targetSelector, this);
          }
        },
        propertiesChanged: function() {
          this.asyncApply();
        },
        compositeChanged: function() {
          this.asyncApply();
        },
        durationChanged: function() {
          this.asyncApply();
        },
        fillModeChanged: function() {
          this.asyncApply();
        },
        easingChanged: function() {
          this.asyncApply();
        },
        iterationCountChanged: function() {
          this.asyncApply();
        },
        startDelayChanged: function() {
          this.asyncApply();
        },
        directionChanged: function() {
          this.asyncApply();
        },
        autoplayChanged: function() {
          this.asyncApply();
        },
        doOnChildren: function(inFn) {
          var children = this.children;
          if (!children.length) {
            children = this.webkitShadowRoot ? this.webkitShadowRoot.childNodes : [];
          }
          Array.prototype.forEach.call(children, inFn, this);
        },
        get timingProps() {
          var props = {};
          var timing = {
            fillMode: {},
            easing: {property: 'timingFunction'},
            startDelay: {isNumber: true},
            iterationCount: {isNumber: true, allowInfinity: true},
            direction: {},
            duration: {isNumber: true}
          };
          for (t in timing) {
            if (this[t] !== null) {
              var name = timing[t].property || t;
              props[name] = timing[t].isNumber ? toNumber(this[t], timing[t].allowInfinity) : this[t];
            }
          }
          return props;
        },
        get animationProps() {
          var props = {};
          if (!this.properties) {
            this.doOnChildren(function(c) {
              if (c.tagName && c.tagName.toLowerCase() === 'polymer-animation-prop') {
                var property = c.name;
                var values = [];
                this.doOnChildren.call(c, function(f) {
                  values.push(f.properties);
                });
                props[property] = values;
              }
            });
          } else {
            for (k in this.properties) {
              if (this.properties.hasOwnProperty(k)) {
                props[k] = this.properties[k];
              }
            }
          }
          if (this.composite) {
            props.operation = this.composite;
          }
          return props;
        },
        monitor: function() {
          // TODO(sorvell): adding brittle support for an end event
          if (this.player && this.player.source &&
              this.player.source._isPastEndOfActiveInterval()) {
            this.complete();
          } else {
            requestAnimationFrame(this.monitor.bind(this));
          }
        },
        // intended for user implementation
        complete: function() {
          this.fire('complete');
        }
      });
    })();
  </script>
</polymer-element>
